
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.media.opengl.DebugGL2;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GL4;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.GLEventListener;
import javax.swing.JFrame;

import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.awt.TextRenderer;
import com.jogamp.opengl.util.gl2.GLUT;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureIO;
import com.jogamp.opengl.util.texture.awt.AWTTextureIO;

public class OpenGLGame extends GLCanvas implements GLEventListener, KeyListener {
	
	Road road;
	Vehicle vehicle;
	FPSAnimator animator;
	JFrame frame;
	GLU glu;
	int frameRate = 60;
	final float minSpeed = 1.0f;
	float speed = minSpeed;
	float deltaSpeed = 0.0f;
	float speedIncrease = 1.0f / frameRate;
	float speedDecay = 0.0f / frameRate;
	float gravity = 0.05f / frameRate;
	int nFrames = 0;
	public float paddleX = 0;
	public float maxPaddleX = 128;
	public Point3D currentPosition = new Point3D(0, 0, 0);
	public Point3D nextPosition = new Point3D(0, 0, 0);
	public float theta = 0;
	public float height = 2.0f;
	public ArduinoComm portReader;
	public long frameTime = System.currentTimeMillis();
	TextRenderer renderer;
	public Texture surfaceTexture;
	
	
	static final int leftRearIndex = Constants.leftRearIndex;
	static final int rightRearIndex = Constants.rightRearIndex;
	static final int frontIndex = Constants.frontIndex;	
	static final int xIndex = Constants.xIndex;
	static final int yIndex = Constants.yIndex;
	static final int zIndex = Constants.zIndex;
	float eye[] = {0.0f, 0.0f, 0.0f};
	float camera[] = {0.0f, 0.0f, 0.0f};
	float vehicleMatrix[][] = {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}};
	
	public void refreshPaddleX(int intVal, int fraction) {
		float inertia = 2.0f;
		float newPaddleX = (intVal + fraction / maxPaddleX);
		float deltaPaddleX = newPaddleX - paddleX;
		if(Math.abs(deltaPaddleX / inertia) < 1.5f) return;
		paddleX = paddleX + deltaPaddleX / inertia;
		theta = (float) (0.7f * Math.PI) * paddleX / maxPaddleX - (float) (Math.PI * 0.35f);
		if(paddleX > (maxPaddleX / 2 - 32) && paddleX < (maxPaddleX / 2 + 32) && speed == 0.01f) {
			//speed = 30.0f / frameRate;
		}
	}
	
	public static void main(String[] args) {
	    OpenGLGame canvas = new OpenGLGame();
		//road = new Road();
	}
	
	public OpenGLGame() {
	    GLCapabilities capabilities = new GLCapabilities(null);
        capabilities.setRedBits(8);
        capabilities.setBlueBits(8);
        capabilities.setGreenBits(8);
        capabilities.setDepthBits(32);
		addGLEventListener(this);
		addKeyListener(this);
		portReader = new ArduinoComm(this);
	    frame = new JFrame("Racing Game");
	    frame.getContentPane().add(this, BorderLayout.CENTER);
	    frame.setSize(1600, 840);
	    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    frame.setVisible(true);
	    this.requestFocus();
	}

	public void init(GLAutoDrawable drawable) {
		road = new Road(this);
		vehicle = new Vehicle();
		glu = new GLU();
		GL2 gl = drawable.getGL().getGL2();
	    // drawable.setGL(new DebugGL2(gl));
	    // Global settings.
		/* Configure the Open GL renderer */
		/*Enable back face culling*/
		//gl.glEnable(GL2.GL_CULL_FACE);
		//gl.glCullFace(GL2.GL_BACK);
		//gl.glFrontFace(GL2.GL_CCW);
		/* Depth buffer setup */
		gl.glClearDepthf( 1.0f );
		/* Enables Depth Testing */
		gl.glEnable(GL2.GL_DEPTH_TEST );
		gl.glDepthMask(true);
		/* The Type Of Depth Test To Do */
		gl.glDepthFunc(GL2.GL_LEQUAL );
	    gl.glShadeModel(GL2.GL_SMOOTH);
	    gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
	    // Start animator (which should be a field).
	    renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36), true, true);
	    animator = new FPSAnimator(this, frameRate);
	    animator.start();
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL2 gl = drawable.getGL().getGL2();
        gl.glViewport(0, 0, width, height);
	}

	public void display(GLAutoDrawable drawable) {
		// check to see if eye is in bounds
		if(!animator.isAnimating()) return;
		speed += deltaSpeed;
		speed -= speedDecay;
		if(speed < 0) speed = 0.0f;
		if(speed < minSpeed) speed = minSpeed;
		nextPosition.x = currentPosition.x + (float) Math.sin(theta) * speed;
		nextPosition.z = currentPosition.z + (float) Math.cos(theta) * speed;
		nextPosition.y = road.getY((float) nextPosition.z) + height;
		for(Road.Wall wall: road.getWalls()) {
			if(wall.lineIntersects(currentPosition, nextPosition)) {
				Line2D vehicleLine = new Line2D.Double(currentPosition.x, currentPosition.z, nextPosition.x, nextPosition.z);
				Line2D wallLine = wall.getLineSegment();
				double crashAngle = MathUtils.getAngleBetween(wallLine, vehicleLine);
				Point2D intersection =  MathUtils.getIntersection(wallLine, vehicleLine);
				nextPosition.x = currentPosition.x;
				nextPosition.z = intersection.y; // Y dimension is used for Z position
				nextPosition.y = road.getY((float) nextPosition.z) + height;
				speed *= Math.cos(crashAngle);
			}
			
		}
		GL2 gl = drawable.getGL().getGL2();
		GLUT glut = new GLUT();
		gl.glEnable(GL2.GL_TEXTURE_2D);
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
	    //gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL );      
	    //gl.glPolygonOffset(0.1f, 0.1f ); // Here to get rid of intermittant surface gaps
	    setCamera((GL2) gl, glu);
	    gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		road.draw(gl, (float)nextPosition.x, (float)nextPosition.y, (float)nextPosition.z);
		vehicle.draw(gl, vehicleMatrix);
		//TextUtils.drawCenteredText("Center Paddle", drawable, renderer);
		if(nFrames % frameRate == 0) {
			System.out.println(System.currentTimeMillis() - frameTime);
			frameTime = System.currentTimeMillis();
		}
		//gl.glFlush();
		nFrames++;
		currentPosition.x = nextPosition.x;
		currentPosition.y = nextPosition.y;
		currentPosition.z = nextPosition.z;
	}

	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
			boolean deviceChanged) {
	}

	@Override
	public void dispose(GLAutoDrawable arg0) {
		// TODO Auto-generated method stub
		
	}
	
    private void setCamera(GL2 gl, GLU glu) {
		//Change to projection matrix.
    	int[] perspective = viewVehicleAbove();
		speed += (eye[yIndex] - camera[yIndex]) * gravity;
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        // Perspective.
        glu.gluPerspective(45, 1.0, 1.0, 10000.0);
        glu.gluLookAt(eye[xIndex], eye[yIndex], eye[zIndex], camera[xIndex], camera[yIndex], camera[zIndex], perspective[0], perspective[1], perspective[2]);
        // Change back to model view matrix.
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
    }
    
    private int[] viewVehicleBehind() {
    	eye[xIndex] = (float) currentPosition.x;
		eye[yIndex] = road.getY((float) currentPosition.z) + height;
		eye[zIndex] = (float) currentPosition.z;
		camera[xIndex] = (float) nextPosition.x;
		camera[yIndex] = road.getY((float)nextPosition.z) + height;
		camera[zIndex] = (float) nextPosition.z;
		vehicleMatrix[leftRearIndex][xIndex] = (float) nextPosition.x - 0.25f; //(float) Math.sin(theta) - 1;
		vehicleMatrix[leftRearIndex][yIndex] = road.getY((float)nextPosition.z + 0.5f) + height * 0.75f;
		vehicleMatrix[leftRearIndex][zIndex] = (float)nextPosition.z + 0.5f; // + (float) Math.cos(theta) / 2.0f;
		vehicleMatrix[rightRearIndex][xIndex] = (float) nextPosition.x + 0.25f; //(float) Math.sin(theta) + 1;
		vehicleMatrix[rightRearIndex][yIndex] = road.getY((float)nextPosition.z + 0.5f) + height * 0.75f;
		vehicleMatrix[rightRearIndex][zIndex] = (float)nextPosition.z + 0.5f; // + (float) Math.cos(theta) / 2.0f;
		vehicleMatrix[frontIndex][xIndex] = (float) nextPosition.x + (float) Math.sin(theta);
		vehicleMatrix[frontIndex][yIndex] = road.getY((float)nextPosition.z + 1) + height * 0.75f;
		vehicleMatrix[frontIndex][zIndex] = (float)nextPosition.z + 1; // + (float) Math.cos(theta);
		int[] returnVal = {0, 1, 0};
		return returnVal;
    }
    
    private int[] viewVehicleAbove() {
		float vmlrx = vehicleMatrix[leftRearIndex][xIndex] = (float) nextPosition.x - Vehicle.width;
		float vmlry = vehicleMatrix[leftRearIndex][yIndex] = road.getY((float) nextPosition.z) + height;
		float vmlrz = vehicleMatrix[leftRearIndex][zIndex] = (float) nextPosition.z;
		float vmrrx = vehicleMatrix[rightRearIndex][xIndex] = (float) nextPosition.x + Vehicle.width;
		float vmrry = vehicleMatrix[rightRearIndex][yIndex] = road.getY((float) nextPosition.z) + height;
		float vmrrz = vehicleMatrix[rightRearIndex][zIndex] = (float) nextPosition.z;
		float vmfx = vehicleMatrix[frontIndex][xIndex] = (float) nextPosition.x;
		float vmfy = vehicleMatrix[frontIndex][yIndex] = road.getY((float) nextPosition.z + Vehicle.length) + height;
		float vmfz = vehicleMatrix[frontIndex][zIndex] = (float) nextPosition.z + Vehicle.length;
		float centerX = (float) nextPosition.x;
		float centerY = road.getY((float) nextPosition.z + vehicle.length / 2.0f) + height;
		float centerZ = (float) nextPosition.z;
		vehicleMatrix[leftRearIndex][xIndex] = centerX + (vmlrx - centerX) * (float) Math.cos(theta) + (vmlrz - centerZ)* (float) Math.sin(theta);
		vehicleMatrix[leftRearIndex][zIndex] = centerZ - (vmlrx - centerX) * (float) Math.sin(theta) + (vmlrz - centerZ)* (float) Math.cos(theta);
		vehicleMatrix[rightRearIndex][xIndex] = centerX + (vmrrx - centerX) * (float) Math.cos(theta) + (vmrrz - centerZ)* (float) Math.sin(theta);
		vehicleMatrix[rightRearIndex][zIndex] = centerZ - (vmrrx - centerX) * (float) Math.sin(theta) + (vmrrz - centerZ)* (float) Math.cos(theta);
		vehicleMatrix[frontIndex][xIndex] = centerX + (vmfx - centerX) * (float) Math.cos(theta) + (vmfz - centerZ) * (float) Math.sin(theta);
		vehicleMatrix[frontIndex][zIndex] = centerZ - (vmfx - centerX) * (float) Math.sin(theta) + (vmfz - centerZ) * (float) Math.cos(theta);
       	eye[xIndex] = (float) currentPosition.x;
    	eye[yIndex] = road.getY((float) currentPosition.z) + height * 101.0f;
    	eye[zIndex] = (float) currentPosition.z;
    	camera[xIndex] = (float) currentPosition.x;
    	camera[yIndex] = road.getY((float)currentPosition.z) + height * 100.0f;
    	camera[zIndex] = (float)currentPosition.z;
		int[] returnVal = {0, 0, 1};
		return returnVal;
    }
    
    public void resetGame() {
    	//animator.stop();
    	currentPosition = new Point3D(0, 0, 0);
    	nextPosition = new Point3D(0, 0, 0);
    	speed = 0.0f;
    	road = new Road(this);
    	//animator.start();
    }

	@Override
	public void keyPressed(KeyEvent arg0) {
		System.out.println("Key Pressed");
		if(arg0.getKeyCode() == KeyEvent.VK_S) {
			deltaSpeed = speedIncrease;
			portReader.turnLEDOn();
			return;
		}
		if(arg0.getKeyCode() == KeyEvent.VK_D) {
			deltaSpeed = - speedIncrease;
			return;
		}
		if(arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
			resetGame();
			return;
		}	
	}

	@Override
	public void keyReleased(KeyEvent arg0) {
		portReader.turnLEDOff();
		deltaSpeed = 0.0f;
		
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

}
